//-----------------------------------------------------------------------------
//
// serverHandler.c
//
// Group:
//    Doug Kline,          dakline@ncsu.edu
//    Mauricio Del Carpio, mgdelcar@ncsu.edu
//    Josh Jodrey,         jdjodrey@ncsu.edu
//
//
// This class handles a single HTTP GET from the server.
//
//-----------------------------------------------------------------------------

#include <linux/ctype.h>
#include <linux/syscalls.h>

#include "reqHandler.h"
#include "serverHandler.h"
#include "cache.h"
#include "NAT.h"


#define BIGBUFSIZE 1024*64

// Debug facility, see kcache.c
extern int debug;

extern struct Config g_config;

static int sendStrToServer(ksocket_t server_p, char *str, int pause);
static int notModified(char *bigBuf_p);
void updateLastMod(struct cacheItem *item_p);
void findPortNumber(struct cacheItem *item_p);


// Some structures for reference
//
// struct addrinfo
// {
//   int ai_flags;			      /* Input flags.  */
//   int ai_family;		         /* Protocol family for socket.  */
//   int ai_socktype;		      /* Socket type.  */
//   int ai_protocol;		      /* Protocol for socket.  */
//   socklen_t ai_addrlen;		   /* Length of socket address.  */
//   struct sockaddr *ai_addr;	/* Socket address for socket.  */
//   char *ai_canonname;		   /* Canonical name for service location.  */
//   struct addrinfo *ai_next;	/* Pointer to next in list.  */
// };
//
// struct sockaddr
// {
//    unsigned short    sa_family;   //address family, AF_xxx
//    char              sa_data[14]; // 14 bytes of protocol address
// };
//
// struct sockaddr_in
// {
//    short              sin_family;   // e.g. AF_INET, AF_INET6
//    unsigned short     sin_port;     // e.g. htons(3490)
//    struct in_addr     sin_addr;     // see struct in_addr, below
//    char               sin_zero[8];  // zero this if you want to
// };
//
// struct in_addr
// {
//    unsigned long s_addr;    // load with inet_pton()
// };
//




//-----------------------------------------------------------------------------
// serverGET()
//
// This function gets the requested object from the the real server.
//
// The item needs to be locked before this is called.
//
//-----------------------------------------------------------------------------
int serverGET(char *command_p, struct cacheItem *item_p)
{
   int err;
   struct sockaddr_in serverAddr;
   void *bigBuf_p;
   ksocket_t server_p;
   int window_size = 16 * 1024;
   int reuse = 1;
   int done = 0;
   int count;
   int retVal = 1;
   int found;
  
   if ( item_p == NULL)
   {
      printk(KERN_ERR "ERROR: serverGET entered with NULL pointers\n");
      return 1;
   }
   
   // Is the port number specfied in the command?
   findPortNumber(item_p);


   memset(&serverAddr, 0, sizeof(serverAddr));
   serverAddr.sin_family = AF_INET;
   
   // Get the server address which was previously stored in NAT
   found = lookupServerAddr( item_p->clientAddr.sin_addr.s_addr, 
                             item_p->clientAddr.sin_port, 
                             &(serverAddr.sin_addr.s_addr),
                             &(serverAddr.sin_port));

   if ( !found )
   {
      printk(KERN_ERR "serverGET failed to find server address\n");
      return 1;
   }
        
   if(debug) printk(KERN_DEBUG "%s/%s %s:%d\n", item_p->hostname, item_p->path, inet_ntoa(serverAddr.sin_addr), item_p->server_port);
  
   server_p = ksocket( AF_INET, SOCK_STREAM, 0);
   
   if (server_p == NULL)
   {
      printk(KERN_ERR "ERROR: ksocket returned NULL pointer\n");
      return(1);
   }

   //--------------------------------------------------------------------------
   // Set the socket options
   ksetsockopt(server_p, SOL_SOCKET, SO_SNDBUF, 
               (char *)&window_size, sizeof(window_size));

   ksetsockopt(server_p, SOL_SOCKET, SO_RCVBUF, 
               (char *)&window_size, sizeof(window_size));

   ksetsockopt(server_p, SOL_SOCKET, SO_REUSEADDR, 
               (char *)&reuse, sizeof(reuse));


   //--------------------------------------------------------------------------
   // Connect to the socket
   
   err = kconnect(server_p, (struct sockaddr *)&serverAddr, sizeof(serverAddr));

   if (err)
   {
      printk(KERN_ERR "ERROR: failed to connect to the socket\n");
      kclose(server_p);
      server_p = NULL;
      return(1);
   }
   
   // Send the request to the server
   err = sendStrToServer(server_p, command_p, 0/*not pause*/);

   if (err)
   {
      printk(KERN_ERR "ERROR: failed to send string to server\n");
      return(1);
   }
   
   bigBuf_p = kmalloc(BIGBUFSIZE, GFP_KERNEL);

   memset(bigBuf_p, 0, BIGBUFSIZE);
   
   done = 0;
   while( !done )
   {
      count = krecv(server_p, bigBuf_p, BIGBUFSIZE-1, 0);
      
      if (count < 0)
      {
         printk(KERN_ERR "ERROR: failed to read from the socket\n");
         goto error;
      }
     
      if (count == 0)
      {
         done = 1;
      }
      else
      {
        // Store it in the cache item
        appendBuf (item_p, bigBuf_p, count);
      }
      
   } // end while
   
    // Extract the Last-Modified timestamp and update our record
   updateLastMod(item_p);
   
   // Mark the item as valid
   item_p->valid = 1;
     
   retVal = 0;

error:
   kfree(bigBuf_p);

   return retVal;
   
 } // end serverGET() 
   
   
   
   
//-----------------------------------------------------------------------------
// serverConditionalGET()
//
// 
//-----------------------------------------------------------------------------
int serverConditionalGET(struct cacheItem *item_p, int *fromCache_p)
{

   int err;
   void *bigBuf_p;
   ksocket_t server_p;
   struct sockaddr_in serverAddr;
   int retVal = 1;
   int done = 0;
   int count;
   int firstRead = 1;
   int modified = 1;
   int found;

  
   if ( item_p == NULL)
   {
      printk(KERN_ERR "ERROR: serverGET entered with NULL pointers\n");
      return 1;
   }
      
   // Mark the item as invalid
   item_p->valid = 0;
   
   // Assume it is from the cache for now
   *fromCache_p = 1;

   memset(&serverAddr, 0, sizeof(serverAddr));
   serverAddr.sin_family = AF_INET;
   
   // Get the server address which was previously stored in NAT
   found = lookupServerAddr( item_p->clientAddr.sin_addr.s_addr, 
                             item_p->clientAddr.sin_port, 
                             &(serverAddr.sin_addr.s_addr),
                             &(serverAddr.sin_port));

   if ( !found )
   {
      printk(KERN_ERR "serverGET failed to find server address\n");
      return 1;
   }
         
   if(debug) printk(KERN_DEBUG "%s/%s %s:%d\n", item_p->hostname, item_p->path, inet_ntoa(serverAddr.sin_addr), item_p->server_port);
   
   server_p = ksocket( AF_INET, SOCK_STREAM, 0);
    
   err = kconnect(server_p, (struct sockaddr *)&serverAddr, sizeof(serverAddr));

   if (err == -1)
   {
      printk(KERN_ERR "ERROR: failed to connect to the socket\n");
      return 1;
   }
   
   // Send the conditional GET to the server
   bigBuf_p = kmalloc(BIGBUFSIZE, GFP_KERNEL);
   
   if (bigBuf_p == NULL)
   {
      printk(KERN_ERR "ERROR: failed to allocate bigBuf\n");
      return 1;
   }

   memset(bigBuf_p, 0, BIGBUFSIZE);
   
   sprintf (bigBuf_p, "GET /%s HTTP/1.0\r\n"
"If-Modified-Since: %s\r\n\r\n", item_p->path, item_p->lastMod);

   err = sendStrToServer(server_p, bigBuf_p, 1/*pause*/);

   if (err)
   {
      printk(KERN_ERR "ERROR: failed to send string to server\n");
      goto error;
   }
   

   // Now read the response which may be 304 Not Modified or 200 OK

   memset(bigBuf_p, 0, BIGBUFSIZE);
   
   firstRead = 1;
   modified = 1;
   done = 0;
   
   while( !done )
   {
      count = krecv(server_p, bigBuf_p, BIGBUFSIZE-1, 0);
      
      if (count < 0)
      {
         printk(KERN_ERR "ERROR: failed to read from the socket\n");
         goto error;
      }
          
      if (count == 0)
      {
         done = 1;
      }
      else
      {
         if(firstRead)
         {
            firstRead = 0;
            
            if (notModified(bigBuf_p))
            {
               if(debug) printk(KERN_DEBUG "304 Not Modified received back\n");
               modified = 0;
               break;
            }
            else
            {
               *fromCache_p = 0;
               
               // Toss previous cached info
               if (item_p->buf_p)
               {
                  kfree(item_p->buf_p);
               }
               item_p->buf_p = NULL;
               item_p->size  = 0;
               
            }
         }
         
         // Store it in the cache item
         appendBuf (item_p, bigBuf_p, count);
      }
      
   } // end while
   
   if (modified)
   {
      // Extract the Last-Modified timestamp and update our record
      updateLastMod(item_p);
   }
   
   // Mark the item as valid
   item_p->valid = 1;
     
   retVal = 0;

error:
   kfree(bigBuf_p);
   
   return retVal;      

 } // end serverConditionalGET() 
   
   


//-----------------------------------------------------------------------------
// sendStrToServer()
//
// This function sends a string to the server.
//
//-----------------------------------------------------------------------------
int sendStrToServer(ksocket_t server_p, char *str, int pause)
{
   int strLength = strlen(str);

   // Send the string
   int count = ksend(server_p, str, strLength, 0);

   if (count != strLength)
   {
      printk(KERN_ERR "ERROR: failed sending request to server, returned count = %d\n", count);
      return 1;
   }

   if (pause)
   {
      // Don't close connection immediately
      msleep_interruptible(1);
    }

   return 0;

} // end sendStrToServer()




//-----------------------------------------------------------------------------
// notModified()
//
// This function checks the buffer for a 304 Not Modified response
//
//-----------------------------------------------------------------------------
static int notModified(char *bigBuf_p)
{
   char *saveptr_p;
   char *token_p;

   saveptr_p = bigBuf_p;
  
   token_p = strsep(&saveptr_p, " ");
   
   if (token_p == 0)
   {
      return 0;
   }

   token_p = strsep(&saveptr_p, " ");
 
   if (token_p == 0)
   {
      return 0;
   }
   
   if ( !strcmp(token_p, "304" ))
   {
      if(debug) printk(KERN_DEBUG "304 Not Modified received\n");
      return 1;
   }
   
   return 0;

} // end notModified()


//-----------------------------------------------------------------------------
// updateLastMod()
//
// This function searches the buffer to see if Last-Modified header is there.
//
//-----------------------------------------------------------------------------
void updateLastMod(struct cacheItem *item_p)
{
   char *str = item_p->buf_p;
   int count = item_p->size;
   char *buf_p = NULL;
   char *token_p;
   char *saveptr_p;
  
   if (str == NULL)
      return;
      
   buf_p = kmalloc(LASTMOD_BUFSIZE, GFP_KERNEL);
   
   item_p->lastMod[0] = '\0';
   
   while( count > 16) // Last-Modified: 
   {
      if (strPrefixMatch("\r\n\r\n", str))
      {
         // End of header region, didn't find it
         break;
      }
   
      if (strPrefixMatch("Last-Modified: ", str))
      {
         // found it
         memcpy (buf_p, str+15, LASTMOD_BUFSIZE);
         buf_p[LASTMOD_BUFSIZE - 1] = '\0';
 
         saveptr_p = buf_p;
         
         token_p = strsep(&saveptr_p, "\r\n");
         strcpy(item_p->lastMod, token_p);
      }
      
      str++;
      count--;
   }
   
   kfree(buf_p);

} // end updateLastMod()





//-----------------------------------------------------------------------------
// findPortNumber()
//
// This function searches the hostname to see if a port number was specified.
//
// 192.168.0.144:5000
////-----------------------------------------------------------------------------
void findPortNumber(struct cacheItem *item_p)
{
   int val = 0;
   char *token_p;
   char *saveptr_p;
   
   saveptr_p = item_p->hostname;
   
   token_p = strsep(&saveptr_p, ":");
   token_p = strsep(&saveptr_p, ":");
   
   if (token_p == NULL)
      return; // no colon
   
   while( (*token_p != '\0') && isdigit(*token_p))
   {
      val = (val * 10) + *token_p - 48; // convert to a digit
      token_p++;
   }
   
   if ( (val > 0) && (val < 65536) )
   {
      item_p->server_port = val;
      
      printk(KERN_INFO "found port number %d in the command\n", item_p->server_port);
   }
   

} // end findPortNumber()


